<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>函子</title>
</head>

<body>
  函子
</body>
<script>
  // 有状态的容器同时有将自身映射成一个新的同类型对象的map 方法，我们称之为函子
  // 状态保存在函子自身上，同时将自己能映射成一个新的同类型的对象 ，所以就是对象映射成对象
  class Functor {
    constructor(value) {
      this.value = value;
    }
    static of(value) {
      return new Functor(value)
    }
    map(fn) {
      return Functor.of(fn(this.value))
    }
  }
  /*    现在知道为什么有 静态of 方法和map 方法了吧，
    1 可以不断链式d调用 
    2不出现 new 这个操作符（写法太不函数化了） */
  var result = Functor.of(1).map(v => v + 1).map(v => v + 5);
  console.log(result);

  // 上面是最简单的一个函子
  // mayBe 函子可以让你过滤空值，或者不传参数也不报错
  // 发现就是在map 上做了不传参的容错处理，不传参就直接返回自己
  class Functor1 {
    constructor(value) {
      this.value = value;
    }
    static of(value) {
      return new Functor1(value)
    }
    map(fn) {
      return this.value ? Functor1.of(fn(this.value)) : this;
    }
  }
  // result = Functor1.of(1).map( v=> v+1 ).map(v=>v+5);
  // console.log(result);


  // either 函子可以适用于不传参取默认值或者2条路径，传递参数用右边的状态，不传用左边的默认值
  // 所以构造函数与2个参数，代表2种输入值
  class Functor2 {
    constructor(left, right) {
      this.left = left;
      this.right = right;
    }
    static of(left, right) {
      return new Functor2(left, right)
    }
    map(fn) {
      return this.right ? Functor2.of(this.left, fn(this.right)) : Functor2.of(fn(this.left), this.right);
    }
    // 存储器函数，当对value 属性读取的时候返回
    get value() {
      return this.right || this.left;
    }
  }
  // 处理默认值
  // result = Functor2.of(2).map( v=> v+1 ).map(v=>v+5);
  // console.log(result);
  // let response = {name:'张三', gneder:null};
  // result = Functor2.of('张三', response.gneder).map(v=>`性别:${v}`);
  // console.log(result);

  // 处理错误,比如字符串不合法，Json.parse 函数报错
  // 有没有发现这样的函子起到的其实就是if-else 在函数式编程中的表达
  function parseJson(str) {
    try {
      return Functor2.of(null, JSON.parse(str))
    } catch (e) {
      return Functor2.of({ errorMsg: e.message }, null)
    }
  }
  result = parseJson(`{"name:"zhangsna"}`).value;
  console.log(result);

  // Ap 函子，拥有ap 方法
  // ap 函子可以让一个函子内的函数使用另一个函子的值进行计算
  // ap 方法的参数不是参数而是另一个函子
  class Ap {
    constructor(value) {
      this.value = value;
    }
    static of(value) {
      return new Ap(value)
    }
    map(fn) {
      return Ap.of(fn(this.value));
    }
    ap(functor) {
      return Ap.of(this.value(functor.value))
    }
  }
  let A = Ap.of(x => x + 1)
  let B = Ap.of(1)
  result = A.ap(B);

  // monad 函子
  // 函子的值也可以是函子，这样就会多层函子嵌套，monad 的作用就是总是返回一个函子的值
  // monad 函子有一个flatMap 的方法可以将一个嵌套的函子的值取出来，保证返回的永远是一个单层的函子，不会出现嵌套的情况
  // 函子嵌套的情况
  class Functor5 {
    constructor(value) {
      this.value = value;
    }
    static of(value) {
      return new Functor5(value)
    }
    map(fn) {
      return Functor5.of(fn(this.value));
    }
  }
  result = Functor5.of(1) // 返回一个value 属性为1 的对象{value:1}
    // x 是上一步的结果1， 返回值是{value:{value:3}}, 因为fn 为 x=>Functor5.of(x+2)返回一层函子又套上一层
    .map(x => Functor5.of(x + 2))
    .map(x => Functor5.of(x.value * 3)) // x 是 {value:3}, fn 又套一层，返回值{value:{value:9}}
    .map(x => Functor5.of(x.value * 2)); // x 是 {value:9}, fn 又套一层，返回值{value:{value:18}}
  //  console.log(result)  //  {value:{value:18}}
  // 发现只要map 的回调返回值是一个函子,那么后面的的取值和计算会非常的麻烦

  let a1 = [1, 2, 3].map(item => [item + 1]); // [[2],[3],[4]], 取值就变得非常麻烦
  let a2 = [1, 2, 3].flatMap(item => [item + 1]) // [2,3,4]

  // 将这种思想运用到编程上来
  class Functor6 {
    constructor(value) {
      this.value = value;
    }
    static of(value) {
      return new Functor6(value)
    }
    map(fn) {
      return Functor6.of(fn(this.value));
    }
    flatMap(fn) {
      return this.map(fn).join(); // 更简洁一点，map 需要一个函数
      // return Functor6.of(fn(this.value).value)
    }
    join() {
      return this.value;
    }
  }
  // 现在我通过flatMap 就可可以将二重打平，根本原因 fn(this.value) 返回的是一层嵌套，
  //包装起来，如果fn(this.value) 返回更高器啊套，那么也要进行相应的修改
  result = Functor6.of(1)
    .flatMap(x => Functor6.of(x + 2))
    .flatMap(x => Functor6.of(x * 3))
    .flatMap(x => Functor6.of(x + 2))
  console.log(result)

  // 副作用，即使程序和外部进行交互，比如读取问文件，发送请求，io 读写
  // 函数式操作者是通过延迟函数的执行，来控制副作用
  // 这个特殊的函子叫做IO
  // 模拟一个localStoage
  // 可能报错，不能直接执行
  let localStorage = {
    getItem(key) {
      if (key === 'data') {
        return `{"code":0, "userId":1}`
      } else if (key === 1) {
        return `{"userId":1, "name":"张三", “age":18}`
      }
    }
  }
  // 自实现的compose 
  function compose(...funArr) {
    var args = arguments;
    if (!args.length) return;
    var func = (...last) => {
      return funArr.reduceRight(function (p, v) {
        return p = v(p)
      }, last)
    }
    return func;
  }

  class IO {
    constructor(value) {
      this.value = value;
    }
    static of(value) {
      return new IO(value)
    }
    map(fn) {
      // return IO.of(fn(this.value)); 就不能直接执行，因为有副作用 ，将this.value 和 和fn 先组合起来
      // 因为是fn(this.value) , 从右向左执行，所以
      // 而且compose 后面理应调用传参，但是我们并没有，巧妙地利用了闭包，参数本层找不到会沿着作用域链往上查找
      return IO.of(compose(fn, this.value));
    }
    flatMap(fn) {
      return IO.of(compose(x => x.value(), fn, this.value));
    }
    join() {
      return this.value;
    }
    // 真正开始执行
    start(callback) {
      return callback(this.value())
    }
  }
  // 很巧妙的用函数包了一层，因为compose 没有直接调用，所以第一的p 其实是个[], 而v 是()=>localStorage.getItem(key)
  // key 又是上层函数的作用捏， 所以就等于巧妙地舍弃了默认的[] ,自然也就不需要在compose 体内判断第一次是数组还是数值了
  const readByKey = key => new IO(() => localStorage.getItem(key)); // 读取输入，有副作用
  const parseJson1 = str => JSON.parse(str); // 纯
  const write = console.log; // 输出，有副作用

  let r1 = readByKey('data')  // 初始值就是一个函数，()=>localStorage.getItem(key) 是value 并且是一个函数
    .map(parseJson1)
    //  .map(x=>readByKey(x.userId)).value() // map 传入一个fn 的返回值是一函子实例的话，等于有包装一层，必须拿出来这个就很不雅观
    .flatMap((x) => readByKey(x.userId))
    .start(write)  //  等于前面的函数操作都被缓存了下来，到start 才执行


  // Task 函子，解决异步问题，惰性执行
  // 从存储状态的角度将状态全部保存在函数的闭包里面，那么之前模范对象以及new  对象就不需要了
  // 这个即使fp 更本质做法，不借助类以类的实例保存数据的编写范式子, 递归执行，数据被保存在函数调用栈里面的函数内部参数了
  // 改写上面的例子,es6  写法

  const Task = execute => ({
    execute,
    // 链式调用所以map 必须还返回一个对象
    map(fn) {
      return Task(()=>fn(this.execute()))
    },
    flatMap() { },
    start(callback) {
      return callback(this.execute())
    }
  })
  // 异步操作
  const request = key => {
    return new Promise(function (resolve) {  
        const timer = setTimeout(() => {
         
          resolve(localStorage.getItem(key));
          clearTimeout(timer);
        }, 3000);
    })
  }
  // 将初始条件结果data包裹到函数作用域里面,其实就是用闭包包裹参数
  // 发觉这种写法，所有的调用写在then 里面不太好看,我想拿出来，写在外面
  // 怎么做啊，必须在then 里面才能拿到异步的值，而返回值又得是外面创建的Task 对象，外面在套上一层Task 即可
  // 这么写的本质还是等promise 完结后 拿到了数据来进行映射执行，那么外面可以将函数流作为参数穿进去，promsie 状态
  // 改变自然会执行
  // request('data').then(data => {
  //    return Task(()=>data)
  //           .map(parseJson1)
  //           .map(x=>x.userId)
  //           .map(key=>ocalStorage.getItem(key))
  //           .start(write)
  // })

  // 这里函数形参和调用的关系，一定要搞清楚，我就是这里无法理解透彻
  // 这里涉及到了递归，上一个数据处理结果被保存在递归栈中
    
  const Task2 = execute => ({
    execute,
    // 链式调用所以map 必须还返回一个对象，map 执行的是映射所以必须有上一步的返回值
    // 我们promise 内部的resolve 是这里的x=>resolve(fn(x) ), x 就是异步的返回结果
    // 而这里的resolve 其实是最后一步execute 传进去的data=>console.log(data)
    map(fn) {
      return Task2( resolve => execute( x=>resolve(fn(x) ) )) 
    },
    flatMap() { }, 
  })
  // 
  const t2 = url=>Task2( resolve => request(url).then(resolve));
  // excute 里面才是resolve
   t2('data')
     .map(parseJson1)
     .execute(data=>console.log(data))
     
// 总结， 和我的第一个实现本质没有差别
// t2('data') 之后我们得到一个对象o1，这个对象有一个execute 属性值为resolve => request(url).then(resolve)
// map(parseJson1) 我们得到一个新的对象o2 resolve => execute( x=>resolve(fn(x) )，注意这里execute 是o1 的
// 然后execute 操作，传入data=>console.log(data)，这是上一步的resolve，没了，其实就是将函数流保存在闭包内部
// 当pomise 状态改变后，会首先调用 x=>resolve(fn(x)）, x 也即是我们的返回结果，所以先fn 处理然后将结果交给resolve
// 也就是 execute 传入的data=>console.log(data)

</script>

</html>